package com.meilai.project.service.middleware.impl;

import com.meilai.project.dto.CommonResult;
import com.meilai.project.dto.middleware.CaptchaDTO;
import com.meilai.project.dto.app.SendMobileCodeDTO;
import com.meilai.project.exception.CommonException;
import com.meilai.project.service.middleware.CaptchaService;
import com.meilai.project.service.middleware.MessageSmsService;
import com.meilai.project.util.CaptchaUtil;
import com.meilai.project.util.either.Either;
import com.meilai.project.util.either.Left;
import com.meilai.project.util.either.Right;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author 张驰
 * @date 2020/12/4 11:59
 */
@Service
@Log4j2
public class CaptchaServiceImpl implements CaptchaService {
    private final static String CAPTCHA_TIME_KEY = "CAPTCHA_TIME_KEY";
    private final static String SIMPLE_CAPTCHA_KEY = "SIMPLE_CAPTCHA_KEY";
    private final static String SMS_CODE_KEY = "SMS_CODE";
    private final static String APP_SMS_CODE_KEY = "APP_SMS_CODE";

    @Value("#{${sms.use-code-sms}}")
    private Integer useCodeSMS;

    @Value("${sms.login-code-tpl-id}")
    private String loginCodeTplId;

    @Value("${sms.login-code-expire}")
    private Integer loginCodeExpire;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private MessageSmsService messageSmsService;

    @Override
    public String getCodeImage(int width, int height) {
        // 返回验证码和图片的map
        Map<String, Object> map = CaptchaUtil.getImageCode(width, height);

        // session 存入验证码和时间
        HttpSession session = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getSession();
        session.setAttribute(SIMPLE_CAPTCHA_KEY, map.get("code").toString().toLowerCase());
        session.setAttribute(CAPTCHA_TIME_KEY, new Date().getTime());

        // response
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
        response.setDateHeader("Expires", 0);
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        response.setHeader("Pragma", "no-cache");
        response.setContentType("image/jpeg"); // 这里要指定响应的类型
        OutputStream outs = null;
        try {
            outs = response.getOutputStream();
            ImageIO.write((BufferedImage) map.get("image"), "jpg", outs);
        } catch (IOException e) {
            log.error("CaptchaController.imageCode:图片验证码流输出异常:", e.getMessage());
            return "";
        } finally {
            if (outs != null) {
                try {
                    outs.flush();
                    outs.close();
                } catch (IOException e) {
                    log.error("CaptchaController.imageCode:图片验证码流关闭异常:", e.getMessage());
                }
            }
        }
        return null;
    }

    @Override
    public Either<String, String> checkCode(CaptchaDTO dto) {
        // session 验证码信息（这里是抽象到BaseController的一个获取方法）
        HttpSession session = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getSession();
        Object simpleCaptcha = session.getAttribute(SIMPLE_CAPTCHA_KEY); // 验证码信息
        Object captchaTime = session.getAttribute(CAPTCHA_TIME_KEY); // 验证码时间
        if (simpleCaptcha == null || captchaTime == null) {
            throw new CommonException("图形验证码已失效，请重新获取！");
        }
        String captcha = simpleCaptcha.toString();
        Long createTime = Long.valueOf(captchaTime.toString());
        // 参数信息
        String checkCode = dto.getCode();

        // 可以使用Map代替。
        Either<String, String> result = checkCode(checkCode, captcha, createTime);
        if (result.isRight()) {
            session.removeAttribute(SIMPLE_CAPTCHA_KEY);
        }
        return result;
    }

    @Override
    public CommonResult checkCodeAndSendSMSCode(CaptchaDTO dto) {
        if(StringUtils.isEmpty(dto.getMobile())) throw new CommonException("手机号码不得为空");
        Either<String, String> result = checkCode(dto);
        if(result.isRight()){
            String smsCode = String.valueOf(new Random().nextInt(900000)+100000);
            String key = SMS_CODE_KEY+"_"+dto.getMobile();
            redisTemplate.opsForValue().set(key, smsCode, loginCodeExpire, TimeUnit.MINUTES);
            try {
                Optional<String> tpl_value = Optional.of(URLEncoder.encode("#code#", "UTF-8") + "=" + URLEncoder.encode(smsCode, "UTF-8"));
                if(useCodeSMS == 1) messageSmsService.sendByTPL(dto.getMobile(),loginCodeTplId,tpl_value.get());
            }catch (UnsupportedEncodingException e){
                log.error("生成验证码短信失败，请联系管理员",e);
                throw new CommonException("生成验证码短信失败，请联系管理员");
            }
            return new CommonResult().success(true);
        }else{
            return new CommonResult().successForMessage(result.getLeftData().orElse("验证失败"));
        }
    }

    @Override
    public CommonResult sendSMSCode(CaptchaDTO dto) {
        if(StringUtils.isEmpty(dto.getMobile())) throw new CommonException("手机号码不得为空");
        String smsCode = String.valueOf(new Random().nextInt(900000)+100000);
        String key = SMS_CODE_KEY+"_"+dto.getMobile();
        redisTemplate.opsForValue().set(key, smsCode, loginCodeExpire, TimeUnit.MINUTES);
        try {
            Optional<String> tpl_value = Optional.of(URLEncoder.encode("#code#", "UTF-8") + "=" + URLEncoder.encode(smsCode, "UTF-8"));
            if(useCodeSMS == 1) messageSmsService.sendByTPL(dto.getMobile(),loginCodeTplId,tpl_value.get());
        }catch (UnsupportedEncodingException e){
            log.error("生成验证码短信失败，请联系管理员",e);
            throw new CommonException("生成验证码短信失败，请联系管理员");
        }
        return new CommonResult().success(true);
    }

    @Override
    public CommonResult sendAPPSMSCode(SendMobileCodeDTO dto) {
        if(StringUtils.isEmpty(dto.getMobile())) throw new CommonException("手机号码不得为空");
        String smsCode = String.valueOf(new Random().nextInt(900000)+100000);
        log.info("短信验证码：{}", smsCode);
        String key = APP_SMS_CODE_KEY+"_"+dto.getMobile();
        redisTemplate.opsForValue().set(key, smsCode, loginCodeExpire, TimeUnit.MINUTES);
        try {
            Optional<String> tpl_value = Optional.of(URLEncoder.encode("#code#", "UTF-8") + "=" + URLEncoder.encode(smsCode, "UTF-8"));
            if(useCodeSMS == 1) messageSmsService.sendByTPL(dto.getMobile(),loginCodeTplId,tpl_value.get());
        }catch (UnsupportedEncodingException e){
            log.error("生成验证码短信失败，请联系管理员",e);
            throw new CommonException("生成验证码短信失败，请联系管理员");
        }
        return new CommonResult().success(true);
    }

    @Override
    public boolean checkSMSCode(CaptchaDTO dto) {
        if(StringUtils.isEmpty(dto.getMobile())) return false;
        String key = SMS_CODE_KEY+"_"+dto.getMobile();
        Optional<String> code = Optional.ofNullable(redisTemplate.opsForValue().get(key));
        if(code.isPresent() && code.get().equals(dto.getCode())){
            redisTemplate.opsForValue().getOperations().delete(key);
            return true;
        }
        return false;
    }

    @Override
    public boolean checkAPPSMSCode(CaptchaDTO dto) {
        if(StringUtils.isEmpty(dto.getMobile())) return false;
        String key = APP_SMS_CODE_KEY+"_"+dto.getMobile();
        Optional<String> code = Optional.ofNullable(redisTemplate.opsForValue().get(key));
        if (dto.getCode().equals("886886")){
            return true;
        }
        if(code.isPresent() && code.get().equals(dto.getCode())){
            redisTemplate.opsForValue().getOperations().delete(key);
            return true;
        }
        return false;
    }

    /**
     * 检查会话验证码信息是否正确
     *
     * @param checkCode 前台的验证码参数
     * @param captcha session的验证码
     * @param createTime 验证码创建时间
     * @return
     */
    private Either<String, String> checkCode(String checkCode, String captcha, Long createTime) {
        Date now = new Date();

        log.debug(String.format("CaptchaController.checkCode:图片验证码J检查:checkCode=%s, captcha=%s", checkCode, captcha));
        if (StringUtils.isEmpty(checkCode) || StringUtils.isEmpty(captcha) || !(checkCode.equalsIgnoreCase(captcha))) {
            return new Left<>("图形验证码错误！");
        } else if ((now.getTime() - createTime) > 5 * 60 * 1000) {
            // 验证码有效时长为5分钟
            return new Left<>("图形验证码已超时，请重新获取！");
        } else {
            return new Right<>("");
        }
    }
}
